﻿
using System.Collections.Generic;
using System.IO;
using System.Text;
using SuperGo.SilverlightLib.History;

namespace SuperGo.SilverlightLib.Sgf {
	/// <summary>
	/// "..."  : terminal symbols
	/// [...]  : option: occurs at most once
	/// {...}  : repetition: any number of times, including zero
	/// (...)  : grouping
	///   |    : exclusive or
	/// italics: parameter explained at some other place
	/// 
	/// Collection = GameTree { GameTree }
	/// GameTree   = "(" Sequence { GameTree } ")"
	/// Sequence   = Node { Node }
	/// Node       = ";" { Property }
	/// Property   = PropIdent PropValue { PropValue }
	/// PropIdent  = UcLetter { UcLetter }
	/// PropValue  = "[" CValueType "]"
	/// CValueType = (ValueType | Compose)
	/// ValueType  = (None | Number | Real | Double | Color | SimpleText |
	///               Text | Point  | Move | Stone)
	/// </summary>
	public class SgfReader {
		public GoGame Read(TextReader textReader) {
			SgfTokenizer tokenizer = new SgfTokenizer(textReader);
			SgfGameTreeNode[] gameTrees = ReadGameTrees(tokenizer);
			GoGame game = null;
			GameProperties gameProperties = new GameProperties();
			foreach (SgfGameTreeNode gameTreeNode in gameTrees) {
				ProcessGameTreeNode(ref game, gameProperties, gameTreeNode);
			}
			EnsureGameCreated(ref game, gameProperties);
			if (gameProperties.TerritorySpecified) {
				game.MergeStonesMarkedAsDead();
			}
			return game;
		}

		private void ProcessGameTreeNode(ref GoGame game, GameProperties gameProperties, SgfGameTreeNode gameTreeNode) {
			HistoryMark historyMark = null;
			if (game != null) {
				historyMark = game.History.GetMark();
			}

			ProcessSequence(ref game, gameProperties, gameTreeNode.Sequence);
			foreach (SgfGameTreeNode childTreeNode in gameTreeNode.ChildTrees) {
				ProcessGameTreeNode(ref game, gameProperties, childTreeNode);
			}

			if (game != null && historyMark != null) {
				game.History.UndoToMark(historyMark);
			}
		}

		private void ProcessSequence(ref GoGame game, GameProperties gameProperties, IEnumerable<SgfPropertyNode[]> sequence) {
			foreach (SgfPropertyNode[] propertyNodes in sequence) {
				foreach (SgfPropertyNode propertyNode in propertyNodes) {
					ProcessPropertyNode(ref game, gameProperties, propertyNode);
				}
			}
		}

		private void ProcessPropertyNode(ref GoGame game, GameProperties gameProperties, SgfPropertyNode propertyNode) {
			int x, y;
			string value;
			switch (propertyNode.PropIdent) {
				case "GM": // type of game
					// Specifies that we are playing go.
					break;
				case "FF": // file version
					gameProperties.Version = int.Parse(propertyNode.PropValues[0].ValueType.Token.Value);
					break;
				case "AP": // application name and version
					gameProperties.ApplicationName = propertyNode.PropValues[0].Compose.ValueType1.Token.Value;
					gameProperties.ApplicationVersion = propertyNode.PropValues[0].Compose.ValueType2.Token.Value;
					break;
				case "ST":
					gameProperties.VariationMode = int.Parse(propertyNode.PropValues[0].ValueType.Token.Value);
					break;
				case "SZ": // game size
					gameProperties.BoardSize = int.Parse(propertyNode.PropValues[0].ValueType.Token.Value);
					break;
				case "KM": // komi
					gameProperties.Komi = decimal.Parse(propertyNode.PropValues[0].ValueType.Token.Value);
					break;
				case "PW": // white player name
					gameProperties.WhitePlayerName = propertyNode.PropValues[0].ValueType.Token.Value;
					break;
				case "PB": // black player name
					gameProperties.BlackPlayerName = propertyNode.PropValues[0].ValueType.Token.Value;
					break;
				case "DT": // date played
					gameProperties.DateGamePlayed = propertyNode.PropValues[0].ValueType.Token.Value;
					break;
				case "HA": // handicap
					gameProperties.Handicap = int.Parse(propertyNode.PropValues[0].ValueType.Token.Value);
					break;
				case "AB": // add black stone
				case "AW": // add white stone
					EnsureGameCreated(ref game, gameProperties);
					foreach (SgfPropValueNode propValueNode in propertyNode.PropValues) {
						ParseCoordinates(propValueNode.ValueType.Token.Value, out x, out y);
						game.SetStone(x, y, propertyNode.PropIdent == "AB" ? StoneState.Black : StoneState.White);
					}
					break;

				case "TB": // territory black
				case "TW": // territory white
					EnsureGameCreated(ref game, gameProperties);
					while (game.GameState == GameState.BlackToMove || game.GameState == GameState.WhiteToMove) {
						game.Pass();
					}
					foreach (SgfPropValueNode propValueNode in propertyNode.PropValues) {
						ParseCoordinates(propValueNode.ValueType.Token.Value, out x, out y);
						if (game.Board[x, y].State != StoneState.Empty) {
							game.Play(x, y);
						}
						gameProperties.TerritorySpecified = true;
					}
					break;

				case "C": break; // Comment
				case "RE": break; // Results of game
				case "B": // black move
				case "W": // white move
					EnsureGameCreated(ref game, gameProperties);
					value = propertyNode.PropValues[0].ValueType.Token.Value;
					if (value == "" || value == "tt") {
						game.Pass();
					} else {
						ParseCoordinates(value, out x, out y);
						game.Play(x, y);
					}
					break;
				default:
					throw new SgfProcessException("Invalid property '{0}'", propertyNode.PropIdent);
			}
		}

		private void EnsureGameCreated(ref GoGame game, GameProperties gameProperties) {
			if (game == null) {
				game = new GoGame(gameProperties.BoardSize, gameProperties.Handicap, gameProperties.Komi, gameProperties.WhitePlayerName, gameProperties.BlackPlayerName);
			}
		}

		private void ParseCoordinates(string value, out int x, out int y) {
			char charX = value[0];
			char charY = value[1];
			if (char.IsLower(charX)) {
				x = charX - 'a';
			} else {
				x = (charX - 'A') + ('z' - 'a');
			}
			if (char.IsLower(charY)) {
				y = charY - 'a';
			} else {
				y = (charY - 'A') + ('z' - 'a');
			}
		}

		/// <summary>
		/// Collection = GameTree { GameTree }
		/// </summary>
		private SgfGameTreeNode[] ReadGameTrees(SgfTokenizer tokenizer) {
			List<SgfGameTreeNode> results = new List<SgfGameTreeNode>();
			SgfGameTreeNode tree;
			do {
				tree = ReadGameTree(tokenizer);
				if (tree != null) {
					results.Add(tree);
				}
			} while (tree != null);
			return results.ToArray();
		}

		/// <summary>
		/// "(" Sequence { GameTree } ")"
		/// </summary>
		private SgfGameTreeNode ReadGameTree(SgfTokenizer tokenizer) {
			if (tokenizer.StartsWith("(")) {
				SgfGameTreeNode result = new SgfGameTreeNode();
				tokenizer.Chomp("(");
				result.Sequence = ReadSequence(tokenizer);
				SgfGameTreeNode tree;
				do {
					tree = ReadGameTree(tokenizer);
					if (tree != null) {
						result.ChildTrees.Add(tree);
					}
				} while (tree != null);
				tokenizer.Chomp(")");
				return result;
			}
			return null;
		}

		/// <summary>
		/// Node { Node }
		/// </summary>
		private SgfPropertyNode[][] ReadSequence(SgfTokenizer tokenizer) {
			List<SgfPropertyNode[]> results = new List<SgfPropertyNode[]>();
			SgfPropertyNode[] node;
			do {
				node = ReadNode(tokenizer);
				if (node != null) {
					results.Add(node);
				}
			} while (node != null);
			return results.ToArray();
		}

		/// <summary>
		/// ";" { Property }
		/// </summary>
		private SgfPropertyNode[] ReadNode(SgfTokenizer tokenizer) {
			if (!tokenizer.StartsWith(";")) {
				return null;
			}
			tokenizer.Chomp(";");
			List<SgfPropertyNode> results = new List<SgfPropertyNode>();
			SgfPropertyNode node;
			do {
				node = ReadProperty(tokenizer);
				if (node != null) {
					results.Add(node);
				}
			} while (node != null);
			return results.ToArray();
		}

		/// <summary>
		/// PropIdent PropValue { PropValue }
		/// </summary>
		private SgfPropertyNode ReadProperty(SgfTokenizer tokenizer) {
			string propIdent = ReadPropIdent(tokenizer);
			if (propIdent == null) {
				return null;
			}
			SgfPropValueNode[] propValues = ReadPropValues(tokenizer);
			SgfPropertyNode result = new SgfPropertyNode {
				PropIdent = propIdent,
				PropValues = propValues
			};
			return result;
		}

		/// <summary>
		/// UcLetter { UcLetter }
		/// </summary>
		private string ReadPropIdent(SgfTokenizer tokenizer) {
			if (!tokenizer.StartsWith(SgfToken.UcLetter)) {
				return null;
			}
			StringBuilder sb = new StringBuilder();
			do {
				SgfToken token = tokenizer.Chomp(SgfToken.UcLetter);
				sb.Append(token.Value);
			} while (tokenizer.StartsWith(SgfToken.UcLetter));
			return sb.ToString();
		}

		private SgfPropValueNode[] ReadPropValues(SgfTokenizer tokenizer) {
			List<SgfPropValueNode> results = new List<SgfPropValueNode>();
			SgfPropValueNode node;
			do {
				node = ReadPropValue(tokenizer);
				if (node != null) {
					results.Add(node);
				}
			} while (node != null);
			return results.ToArray();
		}

		/// <summary>
		/// "[" CValueType "]"
		/// CValueType = (ValueType | Compose)
		/// Compose    = ValueType ":" ValueType
		/// </summary>
		private SgfPropValueNode ReadPropValue(SgfTokenizer tokenizer) {
			if (!tokenizer.StartsWith("[")) {
				return null;
			}
			SgfPropValueNode result = new SgfPropValueNode();
			tokenizer.Chomp("[");
			SgfValueTypeNode valueType = ReadValueType(tokenizer);
			if (tokenizer.StartsWith(":")) {
				result.Compose = new SgfComposeNode();
				result.Compose.ValueType1 = valueType;
				tokenizer.Chomp(":");
				result.Compose.ValueType2 = ReadValueType(tokenizer);
			} else {
				result.ValueType = valueType;
			}
			tokenizer.Chomp("]");
			return result;
		}

		/// <summary>
		/// ValueType  = (None | Number | Real | Double | Color | SimpleText |
		///               Text | Point  | Move | Stone)
		/// </summary>
		private SgfValueTypeNode ReadValueType(SgfTokenizer tokenizer) {
			SgfValueTypeNode result = new SgfValueTypeNode {
				Token = tokenizer.Chomp(new[] {
                     SgfToken.None,
                     SgfToken.Number,
                     SgfToken.Real,
                     SgfToken.Double,
                     SgfToken.Color,
                     SgfToken.SimpleText,
                     SgfToken.Text,
                     SgfToken.Point,
                     SgfToken.Move,
                     SgfToken.Stone
                 })
			};
			return result;
		}

		private class GameProperties {
			public string DateGamePlayed { get; set; }
			public int Version { get; set; }
			public string ApplicationName { get; set; }
			public string ApplicationVersion { get; set; }
			public int VariationMode { get; set; }
			public int BoardSize { get; set; }
			public decimal Komi { get; set; }
			public string WhitePlayerName { get; set; }
			public string BlackPlayerName { get; set; }
			public int Handicap { get; set; }
			public bool TerritorySpecified { get; set; }
		}
	}
}
